using GriffinNav.GPS.Logging;
using GriffinNav.GPS.NMEA;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

namespace GriffinNav.GPS
{
    public class GpsData
    {
        public event GenericHandler GpsStarted;
        public event GenericHandler GpsStopped;
        public event GpsErrorHandler OnGpsError;

        public event PositionChangedHandler PositionChanged;
        public event VelocityChangedHandler VelocityChanged;

        private BluetoothSerial mSerialPort = null;
        private Position mPosition = null;
        private Velocity mVelocity = null;
        private GPGGA mGPGGA = GPGGA.Empty; // GPS Fix Data
        private GPVTG mGPVTG = GPVTG.Empty; // Velocity Made Good
        //private GPRMC mGPRMC = GPRMC.Empty;

        private Thread mGpsDataThread = null;

        public GpsData()
        {
            mPosition = new Position();
            mVelocity = new Velocity();
        }

        public GpsData(string comPort, int baudRate)
        {
            try
            {
                // initialize bluetooth serial port
                mSerialPort = new BluetoothSerial(comPort, baudRate);

                // create positioning objects
                mPosition = new Position();
                mVelocity = new Velocity();

                // subscribe to events
                mSerialPort.DataReceived += new BluetoothSerial.DataReceivedHandler(mSerialPort_DataReceived);
                mSerialPort.Started += new BluetoothSerial.GenericHandler(mSerialPort_Started);
                mSerialPort.Stopped += new BluetoothSerial.GenericHandler(mSerialPort_Stopped);
                mSerialPort.OnError += new GpsErrorHandler(mSerialPort_OnError);
            }
            catch (Exception ex)
            {
                Global.LogError("GpsData.GpsData()", ex);

                throw new GPSException(ex, "An error occured during GPS initialization.", "GpsData", "GpsData()");
            }
        }

        private void mSerialPort_OnError(GPSException ex, bool showMessage)
        {
            Global.LogError(ex.Assembly, ex);

            if (OnGpsError != null)
            {
                OnGpsError(ex, showMessage);
            }
        }

        public DeviceState GpsState
        {
            get
            {
                if (mSerialPort != null)
                {
                    return mSerialPort.State;
                }

                return DeviceState.Stopped;
            }
        }

        public string GpsStateString
        {
            get
            {
                if (mSerialPort != null)
                {
                    switch (mSerialPort.State)
                    {
                        case DeviceState.Running:
                            return "Running";
                        default:
                            return "Stopped";
                    }
                }

                return "Stopped";
            }
        }

        public Position Position
        {
            get
            {
                return mPosition;
            }
        }

        public Velocity Velocity
        {
            get
            {
                return mVelocity;
            }
        }

        public void StartGps(string comPort, int baudRate, int readTimeout)
        {
            try
            {
                // check if serial port is initialized
                if (mSerialPort == null)
                {
                    // init serial port
                    mSerialPort = new BluetoothSerial(comPort, baudRate);

                    // set the read time out for the port
                    mSerialPort.ReadTimeout = readTimeout;

                    // subscribe to events
                    mSerialPort.DataReceived += new BluetoothSerial.DataReceivedHandler(mSerialPort_DataReceived);
                    mSerialPort.Started += new BluetoothSerial.GenericHandler(mSerialPort_Started);
                    mSerialPort.Stopped += new BluetoothSerial.GenericHandler(mSerialPort_Stopped);
                    mSerialPort.OnError += new GpsErrorHandler(mSerialPort_OnError);
                }

                if (mGpsDataThread == null)
                {
                    mGpsDataThread = new Thread(new ThreadStart(mSerialPort.StartRead));
                    mGpsDataThread.IsBackground = true;
                    mGpsDataThread.Name = "GPS Data Thread";

                    // start reading GPS data
                    mGpsDataThread.Start();
                }
            }
            catch (Exception ex)
            {
                // reset serial port
                mSerialPort = null;

                Global.LogError("GpsData.StartGps()", ex);

                throw new GPSException(ex, "Unable to read GPS data. Check COM port settings and try again.", "GpsData", "StartGps()");
            }
        }

        public void StopGps()
        {
            try
            {
                if (mGpsDataThread != null)
                {
                    mGpsDataThread.Abort();
                }
            }
            catch (Exception ex)
            {
                Global.LogError("GpsData.StopGps()", ex);

                throw new GPSException(ex, "An error occured while stopping the GPS.", "GpsData", "StopGps()");
            }
        }

        private void mSerialPort_Started()
        {
            if (GpsStarted != null)
            {
                // raise GPS started event
                GpsStarted();
            }
        }

        private void mSerialPort_Stopped()
        {
            if (GpsStopped != null)
            {
                // raise GPS stopped event
                GpsStopped();
            }

            if (mGpsDataThread != null)
            {
                mGpsDataThread = null;
            }
        }

        private void mSerialPort_DataReceived(string sentence)
        {
            try
            {
                // check which sentence was received
                if (sentence.StartsWith(GPGGA.PREFIX)) // GPS Fix Data
                {

                    mGPGGA.Update(sentence); // update fix data
                    mPosition = mGPGGA.Position; // set position

                    if (PositionChanged != null)
                    {
                        // raise position changed event
                        PositionChanged(mPosition);
                    }
                }
                else if (sentence.StartsWith(GPVTG.PREFIX)) // Velocity Made Good
                {
                    mGPVTG.Update(sentence); // update velocity data
                    mVelocity = mGPVTG.Velocity; // set velocity

                    if (VelocityChanged != null)
                    {
                        // raise velocity changed event
                        VelocityChanged(mVelocity);
                    }
                }
                #region GPRMC Handler
                //else if (sentence.StartsWith(GPRMC.PREFIX))
                //{
                //    mGPRMC.Update(sentence);
                //    mVelocity = mGPRMC.Velocity;

                //    if (VelocityChanged != null)
                //    {
                //        VelocityChanged(mVelocity);
                //    }
                //}
                #endregion
            }
            catch (ThreadAbortException) { }
            catch (Exception ex)
            {
                if (OnGpsError != null)
                {
                    OnGpsError(new GPSException(ex, "Error receiving GPS data.", "GpsData", "mSerialPort_DataReceived()"), true);
                }
            }
            
        }
    }
}
